package dryven.compiletime.apt;

import java.io.BufferedWriter;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.PackageElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.ArrayType;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.ErrorType;
import javax.lang.model.type.ExecutableType;
import javax.lang.model.type.NoType;
import javax.lang.model.type.NullType;
import javax.lang.model.type.PrimitiveType;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.type.TypeVariable;
import javax.lang.model.type.TypeVisitor;
import javax.lang.model.type.WildcardType;
import javax.tools.JavaFileObject;

@SupportedAnnotationTypes({"*"})
@SupportedSourceVersion(SourceVersion.RELEASE_6)
public class ControllerMethodParameterProcessor extends AbstractProcessor implements TypeVisitor<Boolean, Void> {

	public static final String MetaDataClassPrefix = "dryven_md_";
	
	@Override
	public boolean process(Set<? extends TypeElement> annotations,
			RoundEnvironment roundEnv) {
		for (Element e : roundEnv.getRootElements()) {
			if(e.getKind()==ElementKind.CLASS) {
				TypeElement classType = (TypeElement)e;
				boolean isRequiredSuperType = classType.getSuperclass().accept(this,null);
				if(isRequiredSuperType) {
					generateMetaDataClass(classType);
				}
			}
		}
		return true;
	}
	
	//TODO don't assume method names are unique within a class
	private void generateMetaDataClass(TypeElement classType) {
		
		try {
			String packageName = ((PackageElement)classType.getEnclosingElement()).getQualifiedName().toString();
			String className = MetaDataClassPrefix+classType.getSimpleName().toString();
			String qualifiedClassName = packageName+"."+className;
			
			JavaFileObject file = processingEnv.getFiler().createSourceFile(qualifiedClassName, classType);
			BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(file.openOutputStream()));
			
			writer.write(String.format("package %s;\n", packageName));
			writer.write(String.format("public class %s {\n", className));
			
			for (Element member : classType.getEnclosedElements()) {
				if(member.getKind()==ElementKind.METHOD) {
					ExecutableElement method = (ExecutableElement)member;
					
					writer.write(String.format("\tpublic static final String[] %s = {",method.getSimpleName()));
					
					int index = 0;
					for (VariableElement param : method.getParameters()) {
						writer.write(String.format("\"%s\"",param.getSimpleName()));
						++index;
						boolean isLast = index == method.getParameters().size();
						
						if(!isLast) {
							writer.write(",");
						}
					}
					
					writer.write("};\n");
				}
			}
			
			writer.write("}");
			writer.close();
			
			processingEnv.getMessager().printMessage(
				javax.tools.Diagnostic.Kind.WARNING,
				String.format("Generated class %s",qualifiedClassName),
				classType
			);
			
		} catch (IOException e) {
			return;
		}
		
		
	}
	
	/**
	 * Checks (recursivly) if the given type inherits from Controller
	 * */
	@Override
	public Boolean visitDeclared(DeclaredType t, Void p) {
		/*TypeElement type = (TypeElement)t.asElement();
		if(type.getQualifiedName().toString().equals(ControllerBase.class.getName())) {
			return true;
		} else if(type.getQualifiedName().toString().equals(Object.class.getName())) {
			return false;
		} else {
			return type.getSuperclass().accept(this, null);
		}*/
		return false;
	}

	@Override
	public Boolean visit(TypeMirror t) {return false;}
	@Override
	public Boolean visit(TypeMirror t, Void p) {return false;}
	@Override
	public Boolean visitArray(ArrayType t, Void p) {return false;}
	@Override
	public Boolean visitError(ErrorType t, Void p) {return false;}
	@Override
	public Boolean visitExecutable(ExecutableType t, Void p) {return false;}
	@Override
	public Boolean visitNoType(NoType t, Void p) {return false;}
	@Override
	public Boolean visitNull(NullType t, Void p) {return false;}
	@Override
	public Boolean visitPrimitive(PrimitiveType t, Void p) {return false;}
	@Override
	public Boolean visitTypeVariable(TypeVariable t, Void p) {return false;}
	@Override
	public Boolean visitUnknown(TypeMirror t, Void p) {return false;}
	@Override
	public Boolean visitWildcard(WildcardType t, Void p) {return false;}
}
